package com.zz.common.api

import java.io.StringWriter

import akka.actor.ActorSystem
import cats.effect.{ConcurrentEffect, Effect, IO, Sync}
import cats.implicits._
import com.zz.ady.common.metrics.MetricsService
import com.zz.common.metrics.SimpleMetrics
import io.prometheus.client.CollectorRegistry
import io.prometheus.client.exporter.common.TextFormat
import io.prometheus.client.hotspot._
import org.http4s.{Header, HttpRoutes, Response, Status}
import org.http4s.dsl.Http4sDsl

object ApiService {
  def apply[F[_]: ConcurrentEffect]()(implicit system: ActorSystem): ApiService[F] = new ApiService[F]()
}

class ApiService[F[_]: ConcurrentEffect]()(implicit system: ActorSystem) extends Http4sDsl[F] {

  val defaultR: HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root =>
      Ok("http4s service works.")
  }

  val pingR: HttpRoutes[F] = HttpRoutes.of[F] {
    case GET -> Root / "api" / "v1" / "ping" =>
      import org.http4s.circe.CirceInstances._
      import org.http4s.circe.CirceEntityEncoder._
      val r = Map("q" -> "ping!", "a" -> "pong!")
      Ok(r, Header("X-Powered-By", "Scala with http4s"))
  }

  val routes: HttpRoutes[F] = defaultR <+> pingR <+> MetricsApi[F]().metricsR

}

object MetricsApi {

  def apply[F[_]: ConcurrentEffect]()(implicit system: ActorSystem): MetricsApi[F] = new MetricsApi

}

class MetricsApi[F[_]](implicit val F: ConcurrentEffect[F], system: ActorSystem) {

  val metricsR: HttpRoutes[F] = MetricsService.of[F](SimpleMetrics(system).registry)

}

final class PrometheusExportService[F[_]: Sync] private (
    val routes: HttpRoutes[F]
  , val collectorRegistry: CollectorRegistry
)

object PrometheusExportService {

  def apply[F[_]: Sync](collectorRegistry: CollectorRegistry): PrometheusExportService[F] =
    new PrometheusExportService(service(collectorRegistry), collectorRegistry)

  def ofRegistry[F[_]: Sync](cr: CollectorRegistry): PrometheusExportService[F] = {
    cr.register(new StandardExports())
    cr.register(new MemoryPoolsExports())
    cr.register(new BufferPoolsExports())
    cr.register(new GarbageCollectorExports())
    cr.register(new ThreadExports())
    cr.register(new ClassLoadingExports())
    cr.register(new VersionInfoExports())
    cr.register(new MemoryAllocationExports())
    new PrometheusExportService(service(cr), cr)
  }

  def build[F[_]: Sync]: F[PrometheusExportService[F]] =
    for {
      cr <- new CollectorRegistry().pure[F]
      _  <- addDefaults(cr)
    } yield new PrometheusExportService[F](service(cr), cr)

  def generateResponse[F[_]: Sync](collectorRegistry: CollectorRegistry): F[Response[F]] =
    Sync[F]
      .delay {
        val writer = new StringWriter
        TextFormat.write004(writer, collectorRegistry.metricFamilySamples)
        writer.toString
      }
      .map(Response[F](Status.Ok).withEntity(_))

  def service[F[_]: Sync](collectorRegistry: CollectorRegistry): HttpRoutes[F] = {
    object dsl extends Http4sDsl[F]
    import dsl._

    HttpRoutes.of[F] {
      case GET -> Root / "metrics" => generateResponse(collectorRegistry)
    }
  }

  def addDefaults[F[_]: Sync](cr: CollectorRegistry): F[Unit] = Sync[F].delay {
    cr.register(new StandardExports())
    cr.register(new MemoryPoolsExports())
    cr.register(new BufferPoolsExports())
    cr.register(new GarbageCollectorExports())
    cr.register(new ThreadExports())
    cr.register(new ClassLoadingExports())
    cr.register(new VersionInfoExports())
    cr.register(new MemoryAllocationExports())
  }

}
